/**
 * 所有版权归 广西梧州 陈锦韬 所有
 *
 * @Title: LoginAuthenticationProvider
 * @Package com.rx.core.bean
 * @Description: 各种登录验证
 * @author: 陈锦韬
 * @date: 2021\6\27 0027
 * @version V1.0
 * @Copyright: 2021 陈锦韬  All rights reserved.
 */
package com.rx.core.bean;

import com.alibaba.fastjson.JSON;
import com.rx.core.enm.LOGIN_CLASS;
import com.rx.core.enm.RESULT_CODE;
import com.rx.core.iface.IUserDetailsService;
import com.rx.core.util.EmptyChecker;
import com.rx.core.util.RxContentHolder;
import com.rx.core.util.RxUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;

import java.util.Map;

/**
 * @author: Administrator
 * @Description: 各种登录验证
 * @date: 2021\6\27 0027
 */
@Slf4j
public class LoginAuthenticationProvider extends DaoAuthenticationProvider {
    private volatile String userNotFoundEncodedPassword;
    private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";

    public LoginAuthenticationProvider(UserDetailsService userDetailsService) {
        super();
        // 这个地方一定要对userDetailsService赋值，不然userDetailsService是null (这个坑有点深)
        setUserDetailsService(userDetailsService);
    }

    private void checkCode(String email,String code){

    }
    private void matches (boolean fromConfig,UserDetails userDetails,UsernamePasswordAuthenticationToken authentication){
        if (authentication.getCredentials() == null) {
            log.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

        String presentedPassword = authentication.getCredentials().toString();

        PasswordEncoder passwordEncoder = getPasswordEncoder();
        if (passwordEncoder instanceof RxBCryptPasswordEncoder){
            RxBCryptPasswordEncoder encoder = (RxBCryptPasswordEncoder)passwordEncoder;
            if (!encoder.matches(fromConfig,presentedPassword, userDetails.getPassword())) {
                log.debug("Authentication failed: password does not match stored value");
                throw new BadCredentialsException(messages.getMessage(
                        "badCredentials",
                        "用户口令错误"));
            }
        }else {
            if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
                log.debug("Authentication failed: password does not match stored value");
                throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials",
                        "Bad credentials"));
            }
        }
    }
    private void checkGrantType(String grantType){
        RxUtil.CheckUtil.condition(!"password".equalsIgnoreCase(grantType),RESULT_CODE.MESSAGE,"grant_type 设置错误");
    }
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
              UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        Map map = (Map)authentication.getDetails();
        String clientId = (String)map.get("client_id");
        String code = (String)map.get("code");
        String userName = (String)map.get("username");
        String grantType = (String)map.get("grant_type");

        if(LOGIN_CLASS.EMAIL.getCode().equalsIgnoreCase(clientId)){
            checkGrantType(grantType);
            checkCode(userName,code);
        }else if(LOGIN_CLASS.PHONE.getCode().equalsIgnoreCase(clientId)){
            checkGrantType(grantType);
            checkCode(userName,code);
            return;
        }else if(LOGIN_CLASS.OTHER.getCode().equalsIgnoreCase(clientId)){
            checkGrantType(grantType);
            return;
        }

        matches(false,userDetails,authentication);
    }
    private void prepareTimingAttackProtection() {
        if (userNotFoundEncodedPassword == null) {
            this.userNotFoundEncodedPassword = this.getPasswordEncoder().encode(USER_NOT_FOUND_PASSWORD);
        }
    }

    private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials().toString();
            this.getPasswordEncoder().matches(presentedPassword, this.userNotFoundEncodedPassword);
        }
    }
    /**
     * 从数据库中获取
     * @param username
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    protected final UserDetails retrieveUserFromDb(String username,UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            Map map = (Map)authentication.getDetails();
            String clientId = (String)map.get("client_id");
            IUserDetailsService iUserDetailsService = (IUserDetailsService)this.getUserDetailsService();
            UserDetails loadedUser = iUserDetailsService.loadUserByLoginClass(username,clientId);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    private UserDetails getUserDetailAndCheck(Authentication authentication){
        // Determine username
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();

        boolean cacheWasUsed = true;
        // 从缓存中获取用户信息
        UserDetails user = this.getUserCache().getUserFromCache(username);

        if (user == null) {
            cacheWasUsed = false;
            try {
                user = retrieveUserFromDb(username, (UsernamePasswordAuthenticationToken)authentication);
            }catch (UsernameNotFoundException notFound) {
                log.debug("User '" + username + "' not found");
                if (hideUserNotFoundExceptions) {
                    // 不要抛出找不到用户的错误。
                    RxUtil.CheckUtil.throwOut(RESULT_CODE.MESSAGE,"[校验错误]:Bad credentials");
                }else {
                    RxUtil.CheckUtil.throwOut(RESULT_CODE.MESSAGE,"[校验错误]:User Not Found");
                }
            }
            RxUtil.CheckUtil.check(user, RESULT_CODE.MESSAGE,"用户返回为空");
        }

        try {
            // 用户过期、锁住、禁用等一些错误
            getPreAuthenticationChecks().check(user);
            // 校验密码 或者验证码等
            additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
        }catch (AuthenticationException exception) {
            if (cacheWasUsed) {
                // 缓存校验错误。从数据库获取最新数据
                cacheWasUsed = false;
                user = retrieveUserFromDb(username, (UsernamePasswordAuthenticationToken) authentication);
                // 验证前校验
                getPreAuthenticationChecks().check(user);

                additionalAuthenticationChecks(user,  (UsernamePasswordAuthenticationToken) authentication);
            } else {
                throw exception;
            }
        }
        // 验证后校验
        getPostAuthenticationChecks().check(user);
        // 将最新的数据存入缓存
        if (!cacheWasUsed) {
            this.getUserCache().putUserInCache(user);
        }
        return user;
    }
    private UserDetails getUserDetail(Authentication authentication){

        LoginUserInfo userInfo = RxContentHolder.getUser();
        RxUtil.CheckUtil.condition(EmptyChecker.isEmpty(userInfo),RESULT_CODE.MESSAGE,"尚未登录！");

        String[] strings = ((String)((Map)authentication.getDetails()).get("username")).split("@&@");
        if(strings.length>1){
            for (LoginUserInfo.OrgInfo orgInfo : userInfo.getTopOrgList()) {
                if(orgInfo.getBid().equals(strings[1])){
                    userInfo.setCurrentOrg(orgInfo);
                }
            }
        }
        String json = JSON.toJSONString(userInfo);
        return User.withUsername(json)
                .password("password")
                .accountExpired(false).authorities("all").build();
    }
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        /**
         * 判断对象属于UsernamePasswordAuthenticationToken
         */
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                () -> messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.onlySupports",
                        "Only UsernamePasswordAuthenticationToken is supported"));

        UserDetails user = null;
        if("change_org".equalsIgnoreCase((String)((Map)authentication.getDetails()).get("code"))){
            user = getUserDetail(authentication);
        }else {
            user = getUserDetailAndCheck(authentication);
        }


        Object principalToReturn = user;

        // 强制转换成字符串
        if (isForcePrincipalAsString()) {
            principalToReturn = user.getUsername();
        }

        return createSuccessAuthentication(principalToReturn, authentication, user);
    }
}
